<template>
  <view>
    <base-node-vue
      :followTheme="props.followTheme"
      :fieldNames="props.fieldNames"
      :data="listData"
    ></base-node-vue>
  </view>
</template>
<script lang="ts" setup>
/**
 * 树状结构
 * @description 用来展示层级，文件夹结构体等.
 */
import { ref, computed, provide, reactive, watch, readonly, toRaw } from "vue";
import { baseNodeData } from "./interface";
import baseNodeVue from "./base-node.vue";
import { getNodeRouter, treeFlat, queryNodeIsParent, queryParentNode } from "./util";
/**
 * 事件说明
 * node-click:节点标题点击时触发的事件。
 * check:节点复选框状态变化时触发的事件。
 * expand:父节点展开/关闭时触发的事件。
 */
const emits = defineEmits([
  "node-click",
  "check",
  "expand",
  "update:selectedId",
  "update:expandedId",
]);
const props = defineProps({
  followTheme: {
    type: [Boolean, String],
    default: true,
  },
  color: {
    type: String,
    default: "primary",
  },
  //父节点打开时的图标。
  expandedIconOpen: {
    type: String,
    default: "tmicon-sort-down",
  },
  //父节点被关闭时显示的图标。
  expandedIconClose: {
    type: String,
    default: "tmicon-caret-right",
  },
  //允许在节点前添加复选框。
  checkable: {
    type: Boolean,
    default: true,
  },
  //是否允许多选
  multiple: {
    type: Boolean,
    default: true,
  },

  /**
   * 展开的父节点可以使用v-model:expanded-id
   */
  expandedId: {
    type: Array,
    default: () => [],
  },
  /**
   * 默认展开的父节点
   */
  defaultExpandedId: {
    type: Array,
    default: () => [],
  },
  //选中的节点。可以使用v-model:selected-id
  selectedId: {
    type: Array,
    default: () => [],
  },
  //默认选中的节点
  defaultSelectedId: {
    type: Array,
    default: () => [],
  },
  /**
   * 生成树结构的数据。结构必须要有id字段。当然可以通过field-names来映射，如果你的唯一标识字段不是Id的话。
   */
  data: {
    type: Array,
    default: () => [],
    required: true,
  },
  /**
   * 数据结构字段的映射表。
   */
  fieldNames: {
    type: Object,
    default: () => {
      return {
        id: "id",
        text: "text",
      };
    },
  },
  //显示连线。
  showLine: {
    type: [Boolean, String],
    default: true,
  },
});
let listData = ref(props.data);
//所有的节点被平铺为一维。当data改变时就要取交集数据，差集需要删除。
let listDataFlat = [...treeFlat(props.data, props.fieldNames.id)];
//所有父节点
let listParentFlat = queryParentNode(props.data, props.fieldNames.id);
const selectedIds = ref([...new Set([...props.defaultSelectedId, ...props.selectedId])]);
const SelectedExpandedId = ref([
  ...new Set([...props.defaultExpandedId, ...props.expandedId]),
]);
//保存选中的父节点。
const parentIds = ref<Array<string | number>>([]);

//是否单选。
if (!props.multiple && selectedIds.value.length >= 1) {
  selectedIds.value = [selectedIds.value[0]];
}

//分离父和子节点。
let __selectedIdsSet = new Set(listParentFlat);
parentIds.value = selectedIds.value.filter((el) => __selectedIdsSet.has(el));

//过滤错误的id集。意思是如果用户提供的id不在数据props.data结构体中，就需要排除掉。以增加使用者的容错率。
// 2022年11月24日 注释下方两行,可能导致用户配音是先取得选中的值,再设置数据集,会导致过滤掉应有的数据.
// const ___a_selectedIds = new Set(selectedIds.value)
// selectedIds.value = listDataFlat.filter(el=>___a_selectedIds.has(el))
selectedIds.value = selectedIds.value.filter((el) => !__selectedIdsSet.has(el));

const ___a_ExpandedId = new Set(SelectedExpandedId.value);
SelectedExpandedId.value = listDataFlat.filter((el) => ___a_ExpandedId.has(el));

//向下传递当前默认选中的子节点。
provide("TreePareantSelectedIds", readonly(selectedIds));
//向下传递默认打开的父节点
provide("TreePareantSelectedExpandedId", readonly(SelectedExpandedId));
//父节点
provide("TreeParentIds", readonly(parentIds));

function onSelected(key: Array<string | number>) {
  selectedIds.value = [...new Set([...selectedIds.value, ...key])];
  emits("update:selectedId", selectedIds.value);
}
function onUnSelected(key: Array<string | number>) {
  let a = new Set(key);
  selectedIds.value = [...new Set([...selectedIds.value].filter((el) => !a.has(el)))];
  emits("update:selectedId", selectedIds.value);
}
function onCheck(e: baseNodeData) {
  emits("check", e);
}
function onExpand(e: any) {
  SelectedExpandedId.value = [
    ...new Set([...SelectedExpandedId.value, e.data[props.fieldNames.id]]),
  ];
  emits("expand", { ...e.data, expanded: e.expand });
  emits("update:expandedId", SelectedExpandedId.value);
}
function onUnExpand(e: any) {
  SelectedExpandedId.value = [
    ...new Set(
      [...SelectedExpandedId.value].filter((el) => el != e.data[props.fieldNames.id])
    ),
  ];
  emits("expand", { ...e.data, expanded: e.expand });
  emits("update:expandedId", SelectedExpandedId.value);
}
function onSelectedParent(key: Array<string | number>) {
  parentIds.value = [...new Set([...parentIds.value, ...key])];
}
function onUnSelectedParent(key: Array<string | number>) {
  let a = new Set(key);
  parentIds.value = [...new Set([...parentIds.value].filter((el) => !a.has(el)))];
}
function getAllListData() {
  return toRaw(props.data);
}
watch([() => props.expandedId], () => {
  SelectedExpandedId.value = [...props.expandedId];
});
watch(
  [() => props.data],
  () => {
    listData.value = props.data;
    listDataFlat = [...treeFlat(props.data)];
    //所有父节点
    listParentFlat = queryParentNode(props.data, props.fieldNames.id);
    //分离父和子节点。
    let __selectedIdsSet = new Set(listParentFlat);
    parentIds.value = selectedIds.value.filter((el) => __selectedIdsSet.has(el));

    let _a_selectedIds = new Set(selectedIds.value);
    let s = listDataFlat.filter((el) => _a_selectedIds.has(el));
    selectedIds.value = s.filter((el) => !__selectedIdsSet.has(el));
    emits("update:selectedId", selectedIds.value);
    let _a_ExpandedId = new Set(SelectedExpandedId.value);
    SelectedExpandedId.value = listDataFlat.filter((el) => _a_ExpandedId.has(el));
    emits("update:expandedId", SelectedExpandedId.value);
  },
  { deep: true }
);
provide(
  "TreeNodeCheckable",
  computed(() => props.checkable)
);

/** 下方的方法是对象展示的ref方法 **/
/**
 * 改变所有节点状态。
 * checked:
 * true时选中所有节点。
 * false时取消所有节点
 */
function checkAll(checked = true) {
  if (checked == true) {
    onSelected(listDataFlat);
    onSelectedParent(listParentFlat);
  } else {
    selectedIds.value = [];
    onUnSelectedParent(listParentFlat);
  }
  emits("update:selectedId", [...selectedIds.value, ...parentIds.value]);
}
/**
 * 指定节点状态
 * @param {string|number} key 节点id
 * @param {boolean} checked 节点状态
 * description 注意，如果指定的是父节点，将会选中他的所有节节点，反之取消它所有的子节点。
 */
function checkNode(key: string | number, checked: boolean) {
  //先检查 是否存在节点，不存在返回失败。
  if (!new Set(listDataFlat).has(key)) {
    console.error("不存在该节点");
    return false;
  }
  let parentData = queryNodeIsParent(toRaw(props.data), key, props.fieldNames.id);
  //选中的是父节点
  if (parentData) {
    //拆分父和子节点。

    //先平铺所有节点。
    let flat_nodataNodes = treeFlat(parentData.children, props.fieldNames.id);
    let all_parent = new Set(listParentFlat);
    //分离出父节点。
    let flat_nodataParentNodes = [...flat_nodataNodes, key].filter((el) =>
      all_parent.has(el)
    );
    //分离也所有子节点
    let flat_nodataChildNodes = [...flat_nodataNodes, key].filter(
      (el) => !all_parent.has(el)
    );

    if (checked == true) {
      onSelected(flat_nodataChildNodes);
      onSelectedParent(flat_nodataParentNodes);
    } else {
      onUnSelected(flat_nodataChildNodes);
      onUnSelectedParent(flat_nodataParentNodes);
    }
  } else {
    if (checked == true) {
      onSelected([key]);
    } else {
      onUnSelected([key]);
    }
  }
  emits("update:selectedId", [...selectedIds.value, ...parentIds.value]);
  return true;
}
/**
 * 展开或者关闭所有父节点状态
 * @param {Boolean}  checked 指定节点打开还是状态的状态。
 */
function expandAll(checked = true) {
  let flatarray = queryParentNode(toRaw(props.data), props.fieldNames.id);
  if (checked) {
    SelectedExpandedId.value = [...new Set([...SelectedExpandedId.value, ...flatarray])];
  } else {
    SelectedExpandedId.value = [];
  }
  emits("update:expandedId", SelectedExpandedId.value);
}
/**
 * 指定父节点展开状态
 * @param {string|number} key 节点id
 * @param {boolean} checked 节点状态
 */
function expandNode(key: string | number, checked: boolean) {
  //先检查 是否存在节点，不存在返回失败。
  if (!new Set(listDataFlat).has(key)) {
    console.error("不存在该节点");
    return false;
  }
  let parentData = queryNodeIsParent(toRaw(props.data, props.fieldNames.id), key);
  if (!parentData) {
    console.error("该节点非父节点");
    return false;
  }
  if (checked == true) {
    SelectedExpandedId.value = [...new Set([...SelectedExpandedId.value, key])];
  } else {
    SelectedExpandedId.value = [
      ...new Set(SelectedExpandedId.value.filter((el) => el != key)),
    ];
  }
  emits("update:expandedId", SelectedExpandedId.value);
  return true;
}
/**
 * 显示某一节点
 * @param key 需要要打开所在路径显示的节点
 * @description 将会打开它所在的所有父节点
 */
function showNode(key: string | number) {
  //先检查 是否是父节点。
  let nodepath = getNodePath(key);
  let parentData = queryNodeIsParent(toRaw(props.data), key, props.fieldNames.id);
  if (!parentData) {
    nodepath = nodepath.filter((el) => el != key);
  }

  SelectedExpandedId.value = [...new Set([...SelectedExpandedId.value, ...nodepath])];
  emits("update:expandedId", SelectedExpandedId.value);
}
/**
 * 获取选中的节点key数组
 * @param {String}  strategy all:返回所有选中的节点,parent:父子节点都选中时只返回父节点,children:只返回子节点
 */
function getCheckedNodes(strategy = "all") {
  if (strategy == "all") {
    return [...toRaw(selectedIds.value), ...parentIds.value];
  } else if (strategy == "parent") {
    return toRaw(parentIds.value);
  } else if (strategy == "children") {
    return toRaw(selectedIds.value);
  } else {
    return [];
  }
}
/**
 * 获取当前展开的节点
 */
function getExpandedNodes() {
  return toRaw(SelectedExpandedId.value);
}
/**
 * 反回节点路径
 * @param key 节点id
 * @description 从父节点开始一直到本节点的路径数组。
 */
function getNodePath(key: string | number) {
  return getNodeRouter(toRaw(props.data), key, props.fieldNames.id);
}

/**
 * 搜索数据
 * @param keyword 关键词
 */
function searchData(keyword) {
  const loop = (data) => {
    const result = [];
    data.forEach((item) => {
      if (item[props.fieldNames.text].toLowerCase().indexOf(keyword.toLowerCase()) > -1) {
        result.push({ ...item });
      } else if (item.children) {
        const filterData = loop(item.children);
        if (filterData.length) {
          result.push({
            ...item,
            children: filterData,
          });
        }
      }
    });
    return result;
  };

  return loop(originTreeData);
}
defineExpose({
  TreeParaentName: "tmTree",
  onUnSelected,
  onSelected,
  onCheck,
  onExpand,
  onUnExpand,
  onSelectedParent,
  onUnSelectedParent,
  getAllListData,
  checkAll,
  checkNode,
  expandAll,
  expandNode,
  getCheckedNodes,
  getExpandedNodes,
  getNodePath,
  showNode,
});
</script>
